
#include "server.h"

struct Fdinfo
{
    pthread_t tid;
    int fd;
    int epfd;
};
void ignore_sigpipe()
{
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = SIG_IGN;
    sigaction(SIGPIPE, &sa, NULL);
}
int initListenFd(unsigned short port)
{
    // printf("init listen fd\n");
    // 创建监听的套接字
    int acceptfd = socket(AF_INET, SOCK_STREAM, 0);

    if (acceptfd == -1)
    {
        perror("socket");
        return -1;
    }
    int opt = 1;
    // 设置ip可以重复使用 端口复用
    int ret = setsockopt(acceptfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt);
    if (ret == -1)
    {

        perror("setsockopt");
        return -1;
    }

    // 绑定
    struct sockaddr_in addr;
    bzero(&addr, sizeof addr);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    ret = bind(acceptfd, (struct sockaddr *)&addr, sizeof addr);

    // 监听

    ret = listen(acceptfd, 128);
    if (ret == -1)
    {
        perror("listen");
        return -1;
    }
    printf("listen fd is %d\n", acceptfd);
    return acceptfd;
}

int epollrun(int lfd)
{
    int epfd = epoll_create(1);
    printf("epoll fd is %d\n", epfd);
    if (epfd == -1)
    {
        perror("epoll_create");
        return -1;
    }

    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = lfd;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev) == -1)
    {
        perror("epoll_ctl");
        close(epfd);
        return -1;
    }
    // printf("success add acceptfd\n");

    // 接受返回的fd事件
    struct epoll_event events[1024];
    // 最后一个参数是time out，-1表示一直等待
    int size = sizeof(events) / sizeof(struct epoll_event);
    while (1)
    {
        //
        // printf("epoll wait\n");
        int num = epoll_wait(epfd, events, size, -1);
        for (int i = 0; i < num; i++)
        {
            struct Fdinfo *info = (struct Fdinfo *)malloc(sizeof(struct Fdinfo));
            info->fd = events[i].data.fd;
            info->epfd = epfd;

            int fd = events[i].data.fd;
            if (fd == lfd)
            {
                // 建立新连接
                // acceptClient(lfd, epfd);
                pthread_create(&info->tid, NULL, acceptClient, info);
            }
            else
            {
                // 通信的fd ，接受
                // recvHttpRequest(fd, epfd);
                pthread_create(&info->tid, NULL, recvHttpRequest, info);
            }
        }
    }
    return 0;
}

void *acceptClient(void *arg)
{
    struct Fdinfo *info = (struct Fdinfo *)arg;
    int lfd = info->fd;
    int epfd = info->epfd;
    // printf("accept client\n");
    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);
    int clientfd = accept(lfd, (struct sockaddr *)&addr, &len);
    if (clientfd == -1)
    {
        perror("accept");
        return NULL;
    }
    // 设置为非阻塞 加上红黑树
    setnonblock(clientfd);
    struct epoll_event ev1;
    ev1.events = EPOLLIN | EPOLLET;
    ev1.data.fd = clientfd;
    int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev1);
    if (ret == -1)
    {
        perror("epoll_ctl");
        return NULL;
    }
    printf("接受请求 线程：%ld\n", info->tid);
    free(info);
    return NULL;
}

int setnonblock(int fd)
{
    int flag = fcntl(fd, F_GETFL);
    flag |= O_NONBLOCK;
    int ret = fcntl(fd, F_SETFL, flag);
}

void *recvHttpRequest(void *arg)
{
    // printf("recv http request\n");
    struct Fdinfo *info = (struct Fdinfo *)arg;
    int cfd = info->fd;
    int epfd = info->epfd;
    // 读取数据
    // recv(cfd, buf, sizeof buf, 0
    int len = 0;
    int total = 0;
    char buf[4096] = {0};
    char tmp[1024] = {0};
    while ((len = recv(cfd, tmp, sizeof tmp, 0)) > 0)
    {
        if (total + len < sizeof buf)
        {
            memcpy(buf + total, tmp, len);
        }
        total += len;
    }
    // 判断数据是否被接受完 // 读取为-1 表示读完了 读数据失败哦、也是返返回-1
    if (len == -1 && errno == EAGAIN)
    {
        // 解析请求行
        // 找到第一个位\r\n
        char *pt = strstr(buf, "\r\n");

        int reqlen = pt - buf;

        buf[reqlen] = '\0';
        printf("buf:%s\n", buf);
        parseHttpRequest(buf, cfd);
    }
    else if (len == 0)
    {
        // 客户端断开连接
        epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
        close(cfd);
    }
    else
    {
        perror("recv");
    }
    printf("接受数据 线程：%ld\n", info->tid);
    free(info);
    return NULL;
}

int parseHttpRequest(const char *line, int cfd)
{
    // get /xxx/1.jpg http/1.1
    // printf("parse http request\n");
    // printf("line:%s\n", line);
    char method[12];
    char path[1024];
    sscanf(line, "%[^ ] %[^ ]", method, path);
    if (strcasecmp(method, "get") != 0)
    {
        return -1;
    }
    decodeMsg(path, path);
    if (strcmp(path, "/favicon.ico") == 0)
    {
        sendHeadMsg(cfd, 200, "OK", "image/x-icon", 0);
        return 0;
    }
    // 处理客户端请求的静态资源（目录或者文件）
    char *file = NULL;
    if (strcmp(path, "/") == 0)
    {
        file = "./";
    }
    else
    {
        // 这里为可以取消/
        file = path + 1;
    }
    printf("file:%s\n", file);
    // 获取文件的属性
    struct stat st;
    int ret = stat(file, &st);
    // printf("ret:%d\n", ret);
    if (ret == -1)
    {
        // perror("stat");
        // 文件不存在 -- 回复404 最后一个参数 告诉浏览器不知道
        sendHeadMsg(cfd, 404, "Not Found", getFileType(".html"), -1);
        sendfile("404.html", cfd);
        return 0;
    }

    if (S_ISDIR(st.st_mode))
    {
        // 目录
        sendHeadMsg(cfd, 200, "OK", getFileType(".html"), -1);
        sendDir(file, cfd);
        return 0;
    }
    else
    {
        // 文件
        sendHeadMsg(cfd, 200, "OK", getFileType(file), st.st_size);
        sendfile(file, cfd);
    }
    return 0;
}

int sendfile(const char *filename, int cfd)
{
    printf("%s\n", filename);
    // 1 打开文件
    int fd = open(filename, O_RDONLY);
    assert(fd > 0);
#if 0
    while (1)
    {
        char buf[1024];
        int len = read(fd, buf, sizeof buf);
        if (len > 0)
        {
            int ret = send(cfd, buf, len, 0);
            if (errno == EPIPE || errno == ECONNRESET)
            {
                printf("send error: %s\n", strerror(errno));
                close(cfd);
                break;
            }
            usleep(10); // 这非常重要
        }
        else if (len == 0)
        {
            break;
        }
        else
        {
            perror("read");
            return -1;
        }
    }
#else
    off_t offset = 0;

    struct stat file_stat;
    if (fstat(fd, &file_stat) == -1)
    {
        perror("Could not get file size");
        close(fd);
        return -1;
    }
    // int size = lseek(fd, 0, SEEK_END);
    // // 文件指针移动到文件头部
    // lseek(fd, 0, SEEK_SET);

    int size = file_stat.st_size;

    while (offset < size)
    {
        // 不是永远都发送size大小
        ssize_t bytes_sent = sendfile(cfd, fd, &offset, size - offset);
        // printf("ret value:%d\n", ret);
        if (bytes_sent < 0)
        {
            // eagin 表示没数据数据继续进行尝试
            if (errno != EAGAIN && errno != EINTR)
            {
                perror("Failed to send file");
                close(fd);
                return -1;
            }
        }
    }

#endif
    return 0;
}

int sendHeadMsg(int cfd, int status, const char *descr, const char *type, int length)
{
    // 状态行
    char buffer[4096] = {0};
    sprintf(buffer, "http/1.1 %d %s\r\n", status, descr);

    // 响应头
    sprintf(buffer + strlen(buffer), "content-Type: %s\r\n", type);

    // 第三部分\r\n一起拼接了
    sprintf(buffer + strlen(buffer), "content-length: %d\r\n\r\n", length);

    // send
    send(cfd, buffer, strlen(buffer), 0);
    return 0;
}

const char *getFileType(const char *filename)
{
    // a.jpg a.mp4 a.html
    // 从右往左查找'.' ,如果不存在则返回NULL
    const char *dot = strrchr(filename, '.');

    // 如果是NULL的话
    if (dot == NULL)
    {
        return "text/plain; charset=utf-8";
    }
    // 网页
    if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
    {
        return "text/html; charset=utf-8";
    }

    // 图片
    if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
    {
        return "image/jpeg";
    }

    if (strcmp(dot, ".gif") == 0)
    {
        return "image/gif";
    }

    if (strcmp(dot, ".png") == 0)
    {
        return "image/png";
    }

    if (strcmp(dot, ".css") == 0)
    {
        return "text/css";
    }

    if (strcmp(dot, ".au") == 0)
    {
        return "audio/basic";
    }

    if (strcmp(dot, ".wav") == 0)
    {
        return "audio/wav";
    }

    if (strcmp(dot, ".avi") == 0)
    {
        return "video/x-msvideo";
    }

    if (strcmp(dot, ".mov") == 0 || strcasecmp(dot, ".qt") == 0)
    {
        return "video/quicktime";
    }

    if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
    {
        return "video/mpeg";
    }

    if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
    {
        return "model/vrml";
    }
    if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
    {
        return "audio/midi";
    }

    if (strcmp(dot, ".mp3") == 0)
    {
        return "audio/mpeg";
    }

    if (strcmp(dot, ".ogg") == 0)
    {
        return "application/ogg";
    }

    if (strcmp(dot, ".rpac") == 0)
    {
        return "application/x-ns-proxy-autoconfig";
    }

    return "text/plain; charset=utf-8";
}

int sendDir(const char *dir, int cfd)
{
    char buffer[4096] = {0};
    // 凭借网页显示
    sprintf(buffer, "<html><head><title>%s</title></head><body><table>", dir);
    struct dirent **namelist;
    // 第三个参数是过滤器
    // 第四个参数是排序函数
    // 遍历目录的名字
    // 返回值是得到的目录个数
    int n = scandir(dir, &namelist, NULL, alphasort);
    for (int i = 0; i < n; ++i)
    {
        // 取出文件名 ，namelist 指向的是一个指针数组 struct dirent* tmp[];
        char *name = namelist[i]->d_name;

        // 判断拿出来的文件名字是不是还是一个目录
        // 注意这里 的name 并不是一个正确的路径 而是一个文件名字 要和前面的dir 拼接
        char subPath[1024] = {0};
        // 拼接时候的注意细节
        sprintf(subPath, "%s/%s", dir, name);
        struct stat st;
        stat(subPath, &st);

        if (S_ISDIR(st.st_mode))
        {
            // 如果是目录的话 就加一个斜杠
            // <a> 标签
            // <a href="xxx">name</a>
            // 需要转义一下  跳转到目录里面需要加上斜线      %s/
            sprintf(buffer + strlen(buffer),
                    "<tr><td><a href=\"%s/\">%s</a></td><td>%ld</td></tr>", name, name, st.st_size);
        }
        else
        { //
            sprintf(buffer + strlen(buffer),
                    "<tr><td><a href=\"%s\">%s</a></td><td>%ld</td></tr>", name, name, st.st_size);
            //
            // 文件
        }

        send(cfd, buffer, strlen(buffer), 0);
        memset(buffer, 0, sizeof(buffer));
        // 这里释放内存只需要注意
        free(namelist[i]);
    }
    sprintf(buffer + strlen(buffer), "</table></body></html>");
    send(cfd, buffer, strlen(buffer), 0);
    free(namelist);
    return 0;
}

// 将字符转换为整形数
int hexToDec(char c)
{
    if (c >= '0' && c <= '9')
        return c - '0';
    if (c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;
    return 0;
}

void decodeMsg(char *to, const char *from)
{
    for (; *from != '\0'; ++to, ++from)
    {
        if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2]))
        {
            // 使用 hexToDec 将两个十六进制字符转换为整数
            *to = static_cast<char>(hexToDec(from[1]) * 16 + hexToDec(from[2]));
            from += 2; // 跳过这两个十六进制字符
        }
        else
        {
            *to = *from; // 复制普通字符
        }
    }
    *to = '\0'; // 添加字符串终止符
}
